Introdução


Análise de dados com o emprego de agrupamento não hierárquico com o algoritmo k-means em cima de dados sobre distribuição de dialógo em filmes. A análise foi feita com base nos dados do Dataset Polygraph’s Film Dialogue. Informações sobre este dataset e como ele foi gerado encontram-se no seu repositório original.




Data Overview

readr::read_csv(here("data/character_list5.csv"),
                      progress = FALSE,
                      col_types = cols(
                                    script_id = col_integer(),
                                    imdb_character_name = col_character(),
                                    words = col_integer(),
                                    gender = col_character(),
                                    age = col_character()
                                    )) %>%
  mutate(age = as.numeric(age)) -> characters_list
readr::read_csv(here("data/meta_data7.csv"),
                      progress = FALSE,
         col_types = cols(
                        script_id = col_integer(),
                        imdb_id = col_character(),
                        title = col_character(),
                        year = col_integer(),
                        gross = col_integer(),
                        lines_data = col_character()
                        )) %>%
  mutate(title = iconv(title,"latin1", "UTF-8")) -> meta_data


Combinando Dados Originais

left_join(characters_list, 
          meta_data, 
          by=c("script_id")) %>%
  group_by(title, year) %>%
  drop_na(gross) %>%
  ungroup() -> scripts_data
scripts_data %>%
  glimpse()
Observations: 19,387
Variables: 10
$ script_id           <int> 280, 280, 280, 280, 280, 280, 280, 623, 623, 623, 623, 623, 623, 623...
$ imdb_character_name <chr> "betty", "carolyn johnson", "eleanor", "francesca johns", "madge", "...
$ words               <int> 311, 873, 138, 2251, 190, 723, 1908, 328, 409, 347, 2020, 366, 160, ...
$ gender              <chr> "f", "f", "f", "f", "f", "m", "m", "m", "f", "m", "m", "m", "m", "m"...
$ age                 <dbl> 35, NA, NA, 46, 46, 38, 65, NA, 28, NA, 58, 53, 25, 39, 33, NA, 34, ...
$ imdb_id             <chr> "tt0112579", "tt0112579", "tt0112579", "tt0112579", "tt0112579", "tt...
$ title               <chr> "The Bridges of Madison County", "The Bridges of Madison County", "T...
$ year                <int> 1995, 1995, 1995, 1995, 1995, 1995, 1995, 2001, 2001, 2001, 2001, 20...
$ gross               <int> 142, 142, 142, 142, 142, 142, 142, 37, 37, 37, 37, 37, 37, 37, 37, 3...
$ lines_data          <chr> "4332023434343443203433434334433434343434434344344333434443444344233...
scripts_data %>%
  mutate(fem_words = ifelse(gender == "f",words,0),
         man_words = ifelse(gender == "m",words,0)) %>%
  group_by(title, year) %>%
  mutate(total_fem_words = sum(fem_words),
         total_man_words = sum(man_words)) %>%
  filter(total_fem_words !=  0) %>%
  filter(total_man_words !=  0) %>%
    mutate(f_m_ratio = sum(gender == "f")/sum(gender == "m"),
           mean_fem_words = ifelse(sum(gender == "f") == 0, 0, sum(fem_words)/sum(gender == "f")),
           f_m_wordratio = total_fem_words/total_man_words) %>%
  ungroup()  -> scripts_data
scripts_data %>%
  select(title,
         year,
         f_m_ratio,
         f_m_wordratio) %>%
  sample_n(10)

Exploração dos Dados

Proporção entre dialógo feminino e masculino

scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x=f_m_wordratio,
             y=(..count..)/sum(..count..))) +
  geom_histogram(binwidth = 0.1,
                 boundary = 0,
                 fill = "grey",
                 color = "black") +
  labs(y="Frequência Relativa")

  • Em alguns raríssimos exemplos há muito mais dialógo feminino que feminino.
scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  filter(f_m_wordratio < 10) %>%
  ggplot(aes(x=f_m_wordratio,
             y=(..count..)/sum(..count..))) +
  geom_histogram(binwidth = 0.1,
                 fill = "grey",
                 color = "black") +
  labs(y="Frequência Relativa")

  • Uma vez que filtramos os casos mais raros é possível ver que há uma forte domínio do dialógo masculino sobre o feminino nos filmes.
scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x="", 
             y=f_m_wordratio)) +
  geom_violin(fill="grey",
               width=0.5)

  • É ainda mais óbvio:
    • A presença de alguns poucos casos de completo domínio do diálogo feminino
    • O geral domínio do dialógo masculino sobre feminino

Proporção entre personagens femininos e masculinos

scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x=f_m_ratio,
             y=(..count..)/sum(..count..))) +
  geom_histogram(binwidth = 0.1,
                 boundary = 0,
                 fill = "grey",
                 color = "black") +
  scale_x_continuous(breaks = seq(0,10,0.5)) +
  labs(y="Frequência Relativa")

  • É nítido o domínio de personagens masculinos
scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x="", 
             y=f_m_ratio)) +
  geom_violin(fill="grey",
               width=0.5)

  • Além do forte domínio de personangens masculinos é possível ver a existência de algumas instâncias, embora raras de uma avassaladora presença femininina, (e.g 10 vezes mais mulheres que homens).

Média de palavras ditas por personagens femininos

scripts_data %>%
  group_by(title,year) %>%
  unique() %>%
  filter(!mean_fem_words == 0) %>%
  ggplot(aes(x=mean_fem_words,
             y=(..count..)/sum(..count..))) +
  geom_histogram(binwidth = 250,
                 boundary = 0,
                 fill = "grey",
                 color = "black") +
  labs(y="Frequência Relativa") +
    scale_x_continuous(breaks = seq(0,7000,500))

  • Na maior parte dos filmes, em média os personagens femininos falam menos de 1000 palavras.
scripts_data %>%
  group_by(title,year) %>%
  unique() %>%
  filter(!mean_fem_words == 0) %>%
  ggplot(aes(x="", 
             y=mean_fem_words)) +
  geom_violin(fill="grey",
               width=0.5)

  • É possível perceber uma forte queda na quantidade de personagens femininos a partir de 2000 palavras ditas.

Ano do filme

scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x=year)) +
  geom_bar(fill = "grey",
           color = "black") +
  labs(y="Frequência Absoluta")

  • Os filmes são sua maioria recentes, a quase totalidade dos filmes foi lançada a partir dos anos 1990.
scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x="", 
             y=year)) +
  geom_violin(fill="grey",
               width=0.5)

  • Ainda é possível ver uma presença relevante de filmes do começo dos anos 1980.
  • Existem alguns filmes anteriores aos próprio anos 1950.

Faturamento do filme

scripts_data %>%
  group_by(title,year) %>%
  slice(1) %>%
  unique() %>%
  ggplot(aes(x=gross,
             y=(..count..)/sum(..count..))) +
  geom_histogram(binwidth = 50,
                 boundary = 0,
                 fill = "grey",
                 color = "black") +
  labs(y="Frequência Relativa")

  • Faturamento baixo ou razoável para a maior parte dos filmes.
  • Alguns poucos filmes tiveram um faturamento esmagador.
scripts_data %>%
  group_by(title,year) %>%   
  slice(1) %>%
  unique() %>%
  ggplot(aes(x="", 
             y=gross)) +
  geom_violin(fill="grey",
               width=0.5)

  • Resultados similares aos do respectivo histograma.

Aplicando escala apropriada aos dados.

scripts_data %>%
  group_by(title) %>%
  slice(1) %>%
  unique() %>%
  ungroup() %>%
  select(title,
         gross,
         mean_fem_words,
         f_m_ratio,
         f_m_wordratio) -> data
select(data, -title) %>%
mutate_all(funs(scale)) -> scaled_data
scaled_data %>% 
  sample_n(10)




Número K ótimo


Técnicas Aplicadas


Estatística GAP

A estatística GAP compara a solução do agrupamento com cada k com a solução em um dataset onde não há estrutura de grupos.

plot_clusgap = function(clusgap, title="Gap Statistic calculation results"){
    require("ggplot2")
    gstab = data.frame(clusgap$Tab, k=1:nrow(clusgap$Tab))
    p = ggplot(gstab, aes(k, gap)) + geom_line() + geom_point(size=5)
    p = p + geom_errorbar(aes(ymax=gap+SE.sim, ymin=gap-SE.sim), width = .2)
    p = p + ggtitle(title)
    return(p)
}
gaps <- scaled_data %>% 
    clusGap(FUN = kmeans,
            nstart = 20,
            K.max = 8,
            B = 200,
            iter.max=30)
Clustering k = 1,2,..., K.max (= 8): .. done
Bootstrapping, b = 1,2,..., B (= 200)  [one "." per sample]:
.................................................. 50 
.................................................. 100 
...
Quick-TRANSfer stage steps exceeded maximum (= 80900)
............................................... 150 
.................................................. 200 
plot_clusgap(gaps)

  • 3 grupos parece apropiado.

Elbow Method

set.seed(123)
# Compute and plot wss for k = 2 to k = 15.
k.max <- 15
wss <- sapply(1:k.max, 
              function(k){kmeans(scaled_data, k, nstart=50,iter.max = 15 )$tot.withinss})
plot(1:k.max, wss,
     type="b", pch = 19, frame = FALSE, 
     xlab="Number of clusters K",
     ylab="Total within-clusters sum of squares")

  • Pelo Elbow method 3 parece ser um bom número de grupos devido à queda de 3 para 4.

Bayesian Information Criterion

d_clust <- Mclust(as.matrix(scaled_data), G=1:15, 
                  modelNames = mclust.options("emModelNames"))
plot(d_clust$BIC)

  • Visualmente K = 3 representa um ganho mais significativo em termos de BIC (Bayesian Information Criterion)

Hubert Index e D Index

nb <- NbClust(scaled_data, diss=NULL, distance = "euclidean", 
              min.nc=2, max.nc=5, method = "kmeans", 
              index = "all", alphaBeale = 0.1)
*** : The Hubert index is a graphical method of determining the number of clusters.
                In the plot of Hubert index, we seek a significant knee that corresponds to a 
                significant increase of the value of the measure i.e the significant peak in Hubert
                index second differences plot. 
 

*** : The D index is a graphical method of determining the number of clusters. 
                In the plot of D index, we seek a significant knee (the significant peak in Dindex
                second differences plot) that corresponds to a significant increase of the value of
                the measure. 
 
******************************************************************* 
* Among all indices:                                                
* 5 proposed 2 as the best number of clusters 
* 5 proposed 3 as the best number of clusters 
* 1 proposed 4 as the best number of clusters 
* 12 proposed 5 as the best number of clusters 

                   ***** Conclusion *****                            
 
* According to the majority rule, the best number of clusters is  5 
 
 
******************************************************************* 

hist(nb$Best.nc[1,], breaks = max(na.omit(nb$Best.nc[1,])))

  • O índice de Hubert e o índice D sugerem K = 5 como a melhor solução


K Escolhido


Optaremos por 3 grupos pois a maioria dos testes aponta nessa direção, e empiricamente não foi visto ganho no uso de K=5.




K-Means


Agrupamento

n_clusters = 3
scaled_data %>%
    kmeans(n_clusters, iter.max = 100, nstart = 20) -> km
p <- autoplot(km, data=scaled_data, frame = TRUE)  
ggplotly(p)
  • É possível ver que existe uma parcela de filmes cuja separação em um dado grupo não foi completamente feliz pois os grupos se sobrepõe.
row.names(scaled_data) <- data$title
toclust <- scaled_data %>% 
    rownames_to_column(var = "title") 
km = toclust %>% 
    select(-title) %>% 
    kmeans(centers = n_clusters, iter.max = 100, nstart = 20)
km %>% 
    augment(toclust) %>% 
    gather(key = "variável", value = "valor", -title, -.cluster) %>% 
    ggplot(aes(x = `variável`, y = valor, group = title, colour = .cluster)) + 
    geom_point(alpha = 0.2) + 
    geom_line(alpha = .5) + 
    facet_wrap(~ .cluster) +
    coord_flip()



\(\color{red}{\text{Grupo 1}}\) - Em cima do muro

  • Filmes medianos em termos de proporção de personagens femininos, proporção de dialógos dedicados a personagens femininos, média de dialógo feminino e faturamento.


O nome do grupo se refere à expressão que significa não tomar partido.



\(\color{green}{\text{Grupo 2}}\) - We Can Do It!

  • Menor Faturamento
  • Mais dialógo para as mulheres
  • Maior taxa de personagens femininos


We Can Do It! é o grupo de filmes de maior representação feminina, quer seja em proporção de personagens femininos como em proporção e média de dialógos dedicados a personagens femininos. Existe porém uma característica negativa que acompanha este mesmo grupo, pois este é também o grupo das menores taxas de faturamento. Isso sugere uma infeliz associação negativa entre a representação feminina em filmes e o faturamento destes.


O nome do grupo se refere ao famoso cartaz de J. Howard Miller de 1943 incentivado as mulheres a participar no esforço de guerra nas fábricas. 



\(\color{blue}{\text{Grupo 3}}\) - It’s A Man’s Man’s Man’s World

  • Maior faturamento entre todos
  • Menor taxa de dialógo para as mulheres
  • Menor taxa de personagens femininos


It’s A Man’s Man’s Man’s World é o grupo de filmes de menor representação feminina, quer seja em proporção e média de personagens femininos como em proporção de dialógos dedicados a personagens femininos. Existe porém uma característica negativa que acompanha este mesmo grupo, pois este é também o grupo de maiores taxas de faturamento. Isso sugere uma infeliz associação positiva entre ausência de representação feminina em filmes e o faturamento destes.


O nome do grupo se refere à música de James Brown, a qual foi escrita por sua então namorada Betty Jean Newsome como um comentário sobre a relação entre os sexos.


Qualidade da clusterização / Silhueta

dists = scaled_data %>% 
  dist()
scaled_data %>%
    kmeans(3, iter.max = 100, nstart = 20) -> km
silhouette(km$cluster, dists) %>%
   plot(col = RColorBrewer::brewer.pal(4, "Set2"),border=NA)


  • O valor de 0.47 da silhueta significa que a nossa clusterização foi razoável. ヾ(⌐■_■)ノ♪
LS0tCnRpdGxlOiAiRGlzdHJpYnVpw6fDo28gZGUgZGlhbMOzZ28gZW0gZmlsbWVzIgpzdWJ0aXRsZTogJ1F1ZXN0w7VlcyBkZSBHw6puZXJvIGVtIGRpYWzDs2dvcyBkZSBmaWxtZScKYXV0aG9yOiAiSm9zw6kgQmVuYXJkaSBkZSBTb3V6YSBOdW5lcyIKZGF0ZTogMjkvMDYvMjAxOApvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KCjxicj4KCiMgSW50cm9kdcOnw6NvCgo8YnI+Cgo+IEFuw6FsaXNlIGRlIGRhZG9zIGNvbSBvIGVtcHJlZ28gZGUgYWdydXBhbWVudG8gbsOjbyBoaWVyw6FycXVpY28gY29tIG8gYWxnb3JpdG1vIGstbWVhbnMgZW0gY2ltYSBkZSBkYWRvcyBzb2JyZSBkaXN0cmlidWnDp8OjbyBkZSBkaWFsw7NnbyBlbSBmaWxtZXMuIEEgYW7DoWxpc2UgZm9pIGZlaXRhIGNvbSBiYXNlIG5vcyBkYWRvcyBkbyBEYXRhc2V0ICoqUG9seWdyYXBoJ3MgRmlsbSBEaWFsb2d1ZSoqLiBJbmZvcm1hw6fDtWVzIHNvYnJlIGVzdGUgZGF0YXNldCBlIGNvbW8gZWxlIGZvaSBnZXJhZG8gZW5jb250cmFtLXNlIG5vIHNldSAgW3JlcG9zaXTDs3JpbyBvcmlnaW5hbF0oaHR0cHM6Ly9naXRodWIuY29tL21hdHRoZXdmZGFuaWVscy9zY3JpcHRzKS4KCjxicj4KCioqKgoKPGJyPgoKYGBge3Igc2V0dXAsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgpsaWJyYXJ5KGhlcmUpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkodmVnYW4pCmxpYnJhcnkobWNsdXN0KQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShOYkNsdXN0KQpsaWJyYXJ5KGxhdHRpY2UpCmxpYnJhcnkoY2x1c3RlcikKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2dmb3J0aWZ5KQoKdGhlbWVfc2V0KHRoZW1lX2J3KCkpCmBgYAoKIyBEYXRhIE92ZXJ2aWV3CgpgYGB7ciwgd2FybmluZz1GQUxTRX0KcmVhZHI6OnJlYWRfY3N2KGhlcmUoImRhdGEvY2hhcmFjdGVyX2xpc3Q1LmNzdiIpLAogICAgICAgICAgICAgICAgICAgICAgcHJvZ3Jlc3MgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGNvbF90eXBlcyA9IGNvbHMoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjcmlwdF9pZCA9IGNvbF9pbnRlZ2VyKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGltZGJfY2hhcmFjdGVyX25hbWUgPSBjb2xfY2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdvcmRzID0gY29sX2ludGVnZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZGVyID0gY29sX2NoYXJhY3RlcigpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZ2UgPSBjb2xfY2hhcmFjdGVyKCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSkgJT4lCiAgbXV0YXRlKGFnZSA9IGFzLm51bWVyaWMoYWdlKSkgLT4gY2hhcmFjdGVyc19saXN0CgpyZWFkcjo6cmVhZF9jc3YoaGVyZSgiZGF0YS9tZXRhX2RhdGE3LmNzdiIpLAogICAgICAgICAgICAgICAgICAgICAgcHJvZ3Jlc3MgPSBGQUxTRSwKICAgICAgICAgY29sX3R5cGVzID0gY29scygKICAgICAgICAgICAgICAgICAgICAgICAgc2NyaXB0X2lkID0gY29sX2ludGVnZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgaW1kYl9pZCA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSBjb2xfY2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICAgIHllYXIgPSBjb2xfaW50ZWdlcigpLAogICAgICAgICAgICAgICAgICAgICAgICBncm9zcyA9IGNvbF9pbnRlZ2VyKCksCiAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVzX2RhdGEgPSBjb2xfY2hhcmFjdGVyKCkKICAgICAgICAgICAgICAgICAgICAgICAgKSkgJT4lCiAgbXV0YXRlKHRpdGxlID0gaWNvbnYodGl0bGUsImxhdGluMSIsICJVVEYtOCIpKSAtPiBtZXRhX2RhdGEKYGBgCgo8YnI+CgojIyMjIENvbWJpbmFuZG8gRGFkb3MgT3JpZ2luYWlzCgpgYGB7cn0KbGVmdF9qb2luKGNoYXJhY3RlcnNfbGlzdCwgCiAgICAgICAgICBtZXRhX2RhdGEsIAogICAgICAgICAgYnk9Yygic2NyaXB0X2lkIikpICU+JQogIGdyb3VwX2J5KHRpdGxlLCB5ZWFyKSAlPiUKICBkcm9wX25hKGdyb3NzKSAlPiUKICB1bmdyb3VwKCkgLT4gc2NyaXB0c19kYXRhCgpzY3JpcHRzX2RhdGEgJT4lCiAgZ2xpbXBzZSgpCmBgYAoKYGBge3J9CnNjcmlwdHNfZGF0YSAlPiUKICBtdXRhdGUoZmVtX3dvcmRzID0gaWZlbHNlKGdlbmRlciA9PSAiZiIsd29yZHMsMCksCiAgICAgICAgIG1hbl93b3JkcyA9IGlmZWxzZShnZW5kZXIgPT0gIm0iLHdvcmRzLDApKSAlPiUKICBncm91cF9ieSh0aXRsZSwgeWVhcikgJT4lCiAgbXV0YXRlKHRvdGFsX2ZlbV93b3JkcyA9IHN1bShmZW1fd29yZHMpLAogICAgICAgICB0b3RhbF9tYW5fd29yZHMgPSBzdW0obWFuX3dvcmRzKSkgJT4lCiAgZmlsdGVyKHRvdGFsX2ZlbV93b3JkcyAhPSAgMCkgJT4lCiAgZmlsdGVyKHRvdGFsX21hbl93b3JkcyAhPSAgMCkgJT4lCiAgICBtdXRhdGUoZl9tX3JhdGlvID0gc3VtKGdlbmRlciA9PSAiZiIpL3N1bShnZW5kZXIgPT0gIm0iKSwKICAgICAgICAgICBtZWFuX2ZlbV93b3JkcyA9IGlmZWxzZShzdW0oZ2VuZGVyID09ICJmIikgPT0gMCwgMCwgc3VtKGZlbV93b3Jkcykvc3VtKGdlbmRlciA9PSAiZiIpKSwKICAgICAgICAgICBmX21fd29yZHJhdGlvID0gdG90YWxfZmVtX3dvcmRzL3RvdGFsX21hbl93b3JkcykgJT4lCiAgdW5ncm91cCgpICAtPiBzY3JpcHRzX2RhdGEKCnNjcmlwdHNfZGF0YSAlPiUKICBzZWxlY3QodGl0bGUsCiAgICAgICAgIHllYXIsCiAgICAgICAgIGZfbV9yYXRpbywKICAgICAgICAgZl9tX3dvcmRyYXRpbykgJT4lCiAgc2FtcGxlX24oMTApCmBgYAoKIyMgRXhwbG9yYcOnw6NvIGRvcyBEYWRvcyAKCiMjIyBQcm9wb3LDp8OjbyBlbnRyZSBkaWFsw7NnbyBmZW1pbmlubyBlIG1hc2N1bGlubwoKYGBge3J9CnNjcmlwdHNfZGF0YSAlPiUKICBncm91cF9ieSh0aXRsZSx5ZWFyKSAlPiUKICBzbGljZSgxKSAlPiUKICB1bmlxdWUoKSAlPiUKICBnZ3Bsb3QoYWVzKHg9Zl9tX3dvcmRyYXRpbywKICAgICAgICAgICAgIHk9KC4uY291bnQuLikvc3VtKC4uY291bnQuLikpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjEsCiAgICAgICAgICAgICAgICAgYm91bmRhcnkgPSAwLAogICAgICAgICAgICAgICAgIGZpbGwgPSAiZ3JleSIsCiAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siKSArCiAgbGFicyh5PSJGcmVxdcOqbmNpYSBSZWxhdGl2YSIpCmBgYAoKKiBFbSBhbGd1bnMgcmFyw61zc2ltb3MgZXhlbXBsb3MgaMOhIG11aXRvIG1haXMgZGlhbMOzZ28gZmVtaW5pbm8gcXVlIGZlbWluaW5vLiAKCmBgYHtyfQpzY3JpcHRzX2RhdGEgJT4lCiAgZ3JvdXBfYnkodGl0bGUseWVhcikgJT4lCiAgc2xpY2UoMSkgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZmlsdGVyKGZfbV93b3JkcmF0aW8gPCAxMCkgJT4lCiAgZ2dwbG90KGFlcyh4PWZfbV93b3JkcmF0aW8sCiAgICAgICAgICAgICB5PSguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4xLAogICAgICAgICAgICAgICAgIGZpbGwgPSAiZ3JleSIsCiAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siKSArCiAgbGFicyh5PSJGcmVxdcOqbmNpYSBSZWxhdGl2YSIpCmBgYAoKKiBVbWEgdmV6IHF1ZSBmaWx0cmFtb3Mgb3MgY2Fzb3MgbWFpcyByYXJvcyDDqSBwb3Nzw612ZWwgdmVyIHF1ZSBow6EgdW1hIGZvcnRlIGRvbcOtbmlvIGRvIGRpYWzDs2dvIG1hc2N1bGlubyBzb2JyZSBvIGZlbWluaW5vIG5vcyBmaWxtZXMuCgpgYGB7cn0Kc2NyaXB0c19kYXRhICU+JQogIGdyb3VwX2J5KHRpdGxlLHllYXIpICU+JQogIHNsaWNlKDEpICU+JQogIHVuaXF1ZSgpICU+JQogIGdncGxvdChhZXMoeD0iIiwgCiAgICAgICAgICAgICB5PWZfbV93b3JkcmF0aW8pKSArCiAgZ2VvbV92aW9saW4oZmlsbD0iZ3JleSIsCiAgICAgICAgICAgICAgIHdpZHRoPTAuNSkKYGBgCgoqIMOJIGFpbmRhIG1haXMgw7NidmlvOgogICAgKiBBIHByZXNlbsOnYSBkZSBhbGd1bnMgcG91Y29zIGNhc29zIGRlIGNvbXBsZXRvIGRvbcOtbmlvIGRvIGRpw6Fsb2dvIGZlbWluaW5vCiAgICAqIE8gZ2VyYWwgZG9tw61uaW8gZG8gZGlhbMOzZ28gbWFzY3VsaW5vIHNvYnJlIGZlbWluaW5vCgojIyMgUHJvcG9yw6fDo28gZW50cmUgcGVyc29uYWdlbnMgZmVtaW5pbm9zIGUgbWFzY3VsaW5vcyAKCmBgYHtyfQpzY3JpcHRzX2RhdGEgJT4lCiAgZ3JvdXBfYnkodGl0bGUseWVhcikgJT4lCiAgc2xpY2UoMSkgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZ2dwbG90KGFlcyh4PWZfbV9yYXRpbywKICAgICAgICAgICAgIHk9KC4uY291bnQuLikvc3VtKC4uY291bnQuLikpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjEsCiAgICAgICAgICAgICAgICAgYm91bmRhcnkgPSAwLAogICAgICAgICAgICAgICAgIGZpbGwgPSAiZ3JleSIsCiAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLDEwLDAuNSkpICsKICBsYWJzKHk9IkZyZXF1w6puY2lhIFJlbGF0aXZhIikKYGBgCgoqIMOJIG7DrXRpZG8gbyBkb23DrW5pbyBkZSBwZXJzb25hZ2VucyBtYXNjdWxpbm9zCgpgYGB7cn0Kc2NyaXB0c19kYXRhICU+JQogIGdyb3VwX2J5KHRpdGxlLHllYXIpICU+JQogIHNsaWNlKDEpICU+JQogIHVuaXF1ZSgpICU+JQogIGdncGxvdChhZXMoeD0iIiwgCiAgICAgICAgICAgICB5PWZfbV9yYXRpbykpICsKICBnZW9tX3Zpb2xpbihmaWxsPSJncmV5IiwKICAgICAgICAgICAgICAgd2lkdGg9MC41KQpgYGAKCiogQWzDqW0gZG8gZm9ydGUgZG9tw61uaW8gZGUgcGVyc29uYW5nZW5zIG1hc2N1bGlub3Mgw6kgcG9zc8OtdmVsIHZlciBhIGV4aXN0w6puY2lhIGRlIGFsZ3VtYXMgaW5zdMOibmNpYXMsIGVtYm9yYSByYXJhcyBkZSB1bWEgYXZhc3NhbGFkb3JhIHByZXNlbsOnYSBmZW1pbmluaW5hLCAoZS5nIDEwIHZlemVzIG1haXMgbXVsaGVyZXMgcXVlIGhvbWVucykuCgojIyMgTcOpZGlhIGRlIHBhbGF2cmFzIGRpdGFzIHBvciBwZXJzb25hZ2VucyBmZW1pbmlub3MKCmBgYHtyfQpzY3JpcHRzX2RhdGEgJT4lCiAgZ3JvdXBfYnkodGl0bGUseWVhcikgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZmlsdGVyKCFtZWFuX2ZlbV93b3JkcyA9PSAwKSAlPiUKICBnZ3Bsb3QoYWVzKHg9bWVhbl9mZW1fd29yZHMsCiAgICAgICAgICAgICB5PSguLmNvdW50Li4pL3N1bSguLmNvdW50Li4pKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMjUwLAogICAgICAgICAgICAgICAgIGJvdW5kYXJ5ID0gMCwKICAgICAgICAgICAgICAgICBmaWxsID0gImdyZXkiLAogICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIikgKwogIGxhYnMoeT0iRnJlcXXDqm5jaWEgUmVsYXRpdmEiKSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsNzAwMCw1MDApKQoKYGBgCgoqIE5hIG1haW9yIHBhcnRlIGRvcyBmaWxtZXMsIGVtIG3DqWRpYSBvcyBwZXJzb25hZ2VucyBmZW1pbmlub3MgZmFsYW0gbWVub3MgZGUgMTAwMCBwYWxhdnJhcy4KCmBgYHtyfQpzY3JpcHRzX2RhdGEgJT4lCiAgZ3JvdXBfYnkodGl0bGUseWVhcikgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZmlsdGVyKCFtZWFuX2ZlbV93b3JkcyA9PSAwKSAlPiUKICBnZ3Bsb3QoYWVzKHg9IiIsIAogICAgICAgICAgICAgeT1tZWFuX2ZlbV93b3JkcykpICsKICBnZW9tX3Zpb2xpbihmaWxsPSJncmV5IiwKICAgICAgICAgICAgICAgd2lkdGg9MC41KQpgYGAKCiogw4kgcG9zc8OtdmVsIHBlcmNlYmVyIHVtYSBmb3J0ZSBxdWVkYSBuYSBxdWFudGlkYWRlIGRlIHBlcnNvbmFnZW5zIGZlbWluaW5vcyAgYSBwYXJ0aXIgZGUgMjAwMCBwYWxhdnJhcyBkaXRhcy4gCgojIyMgQW5vIGRvIGZpbG1lIAoKYGBge3J9CnNjcmlwdHNfZGF0YSAlPiUKICBncm91cF9ieSh0aXRsZSx5ZWFyKSAlPiUKICBzbGljZSgxKSAlPiUKICB1bmlxdWUoKSAlPiUKICBnZ3Bsb3QoYWVzKHg9eWVhcikpICsKICBnZW9tX2JhcihmaWxsID0gImdyZXkiLAogICAgICAgICAgIGNvbG9yID0gImJsYWNrIikgKwogIGxhYnMoeT0iRnJlcXXDqm5jaWEgQWJzb2x1dGEiKQpgYGAKCiogT3MgZmlsbWVzIHPDo28gc3VhIG1haW9yaWEgcmVjZW50ZXMsIGEgcXVhc2UgdG90YWxpZGFkZSBkb3MgZmlsbWVzIGZvaSBsYW7Dp2FkYSBhIHBhcnRpciBkb3MgYW5vcyAxOTkwLgoKYGBge3J9CnNjcmlwdHNfZGF0YSAlPiUKICBncm91cF9ieSh0aXRsZSx5ZWFyKSAlPiUKICBzbGljZSgxKSAlPiUKICB1bmlxdWUoKSAlPiUKICBnZ3Bsb3QoYWVzKHg9IiIsIAogICAgICAgICAgICAgeT15ZWFyKSkgKwogIGdlb21fdmlvbGluKGZpbGw9ImdyZXkiLAogICAgICAgICAgICAgICB3aWR0aD0wLjUpCmBgYAoKKiBBaW5kYSDDqSBwb3Nzw612ZWwgdmVyIHVtYSBwcmVzZW7Dp2EgcmVsZXZhbnRlIGRlIGZpbG1lcyBkbyBjb21lw6dvIGRvcyBhbm9zIDE5ODAuCiogRXhpc3RlbSBhbGd1bnMgZmlsbWVzIGFudGVyaW9yZXMgYW9zIHByw7NwcmlvIGFub3MgMTk1MC4KCiMjIyBGYXR1cmFtZW50byBkbyBmaWxtZSAKCmBgYHtyfQpzY3JpcHRzX2RhdGEgJT4lCiAgZ3JvdXBfYnkodGl0bGUseWVhcikgJT4lCiAgc2xpY2UoMSkgJT4lCiAgdW5pcXVlKCkgJT4lCiAgZ2dwbG90KGFlcyh4PWdyb3NzLAogICAgICAgICAgICAgeT0oLi5jb3VudC4uKS9zdW0oLi5jb3VudC4uKSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDUwLAogICAgICAgICAgICAgICAgIGJvdW5kYXJ5ID0gMCwKICAgICAgICAgICAgICAgICBmaWxsID0gImdyZXkiLAogICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIikgKwogIGxhYnMoeT0iRnJlcXXDqm5jaWEgUmVsYXRpdmEiKQpgYGAKCiogRmF0dXJhbWVudG8gYmFpeG8gb3UgcmF6b8OhdmVsIHBhcmEgYSBtYWlvciBwYXJ0ZSBkb3MgZmlsbWVzLgoqIEFsZ3VucyBwb3Vjb3MgZmlsbWVzIHRpdmVyYW0gdW0gZmF0dXJhbWVudG8gZXNtYWdhZG9yLgoKYGBge3J9CnNjcmlwdHNfZGF0YSAlPiUKICBncm91cF9ieSh0aXRsZSx5ZWFyKSAlPiUgICAKICBzbGljZSgxKSAlPiUKICB1bmlxdWUoKSAlPiUKICBnZ3Bsb3QoYWVzKHg9IiIsIAogICAgICAgICAgICAgeT1ncm9zcykpICsKICBnZW9tX3Zpb2xpbihmaWxsPSJncmV5IiwKICAgICAgICAgICAgICAgd2lkdGg9MC41KQpgYGAKCiogUmVzdWx0YWRvcyBzaW1pbGFyZXMgYW9zIGRvIHJlc3BlY3Rpdm8gaGlzdG9ncmFtYS4KCiMjIEFwbGljYW5kbyBlc2NhbGEgYXByb3ByaWFkYSBhb3MgZGFkb3MuCgpgYGB7cn0Kc2NyaXB0c19kYXRhICU+JQogIGdyb3VwX2J5KHRpdGxlKSAlPiUKICBzbGljZSgxKSAlPiUKICB1bmlxdWUoKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgc2VsZWN0KHRpdGxlLAogICAgICAgICBncm9zcywKICAgICAgICAgbWVhbl9mZW1fd29yZHMsCiAgICAgICAgIGZfbV9yYXRpbywKICAgICAgICAgZl9tX3dvcmRyYXRpbykgLT4gZGF0YQoKc2VsZWN0KGRhdGEsIC10aXRsZSkgJT4lCm11dGF0ZV9hbGwoZnVucyhzY2FsZSkpIC0+IHNjYWxlZF9kYXRhCgpzY2FsZWRfZGF0YSAlPiUgCiAgc2FtcGxlX24oMTApCmBgYAoKPGJyPgoKKioqCgo8YnI+CgojICBOw7ptZXJvIEsgw7N0aW1vIAoKPGJyPgoKIyMgVMOpY25pY2FzIEFwbGljYWRhcwoKPGJyPgoKIyMjIEVzdGF0w61zdGljYSBHQVAgCgpBIGVzdGF0w61zdGljYSBHQVAgY29tcGFyYSBhIHNvbHXDp8OjbyBkbyBhZ3J1cGFtZW50byBjb20gY2FkYSBrIGNvbSBhIHNvbHXDp8OjbyBlbSB1bSBkYXRhc2V0IG9uZGUgbsOjbyBow6EgZXN0cnV0dXJhIGRlIGdydXBvcy4gCgpgYGB7cn0KcGxvdF9jbHVzZ2FwID0gZnVuY3Rpb24oY2x1c2dhcCwgdGl0bGU9IkdhcCBTdGF0aXN0aWMgY2FsY3VsYXRpb24gcmVzdWx0cyIpewogICAgcmVxdWlyZSgiZ2dwbG90MiIpCiAgICBnc3RhYiA9IGRhdGEuZnJhbWUoY2x1c2dhcCRUYWIsIGs9MTpucm93KGNsdXNnYXAkVGFiKSkKICAgIHAgPSBnZ3Bsb3QoZ3N0YWIsIGFlcyhrLCBnYXApKSArIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludChzaXplPTUpCiAgICBwID0gcCArIGdlb21fZXJyb3JiYXIoYWVzKHltYXg9Z2FwK1NFLnNpbSwgeW1pbj1nYXAtU0Uuc2ltKSwgd2lkdGggPSAuMikKICAgIHAgPSBwICsgZ2d0aXRsZSh0aXRsZSkKICAgIHJldHVybihwKQp9CmBgYAoKYGBge3J9CmdhcHMgPC0gc2NhbGVkX2RhdGEgJT4lIAogICAgY2x1c0dhcChGVU4gPSBrbWVhbnMsCiAgICAgICAgICAgIG5zdGFydCA9IDIwLAogICAgICAgICAgICBLLm1heCA9IDgsCiAgICAgICAgICAgIEIgPSAyMDAsCiAgICAgICAgICAgIGl0ZXIubWF4PTMwKQpgYGAKCmBgYHtyfQpwbG90X2NsdXNnYXAoZ2FwcykKYGBgCgoqIDMgZ3J1cG9zIHBhcmVjZSBhcHJvcGlhZG8uCgojIyMgRWxib3cgTWV0aG9kCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQojIENvbXB1dGUgYW5kIHBsb3Qgd3NzIGZvciBrID0gMiB0byBrID0gMTUuCmsubWF4IDwtIDE1Cgp3c3MgPC0gc2FwcGx5KDE6ay5tYXgsIAogICAgICAgICAgICAgIGZ1bmN0aW9uKGspe2ttZWFucyhzY2FsZWRfZGF0YSwgaywgbnN0YXJ0PTUwLGl0ZXIubWF4ID0gMTUgKSR0b3Qud2l0aGluc3N9KQpwbG90KDE6ay5tYXgsIHdzcywKICAgICB0eXBlPSJiIiwgcGNoID0gMTksIGZyYW1lID0gRkFMU0UsIAogICAgIHhsYWI9Ik51bWJlciBvZiBjbHVzdGVycyBLIiwKICAgICB5bGFiPSJUb3RhbCB3aXRoaW4tY2x1c3RlcnMgc3VtIG9mIHNxdWFyZXMiKQpgYGAKCiogUGVsbyBFbGJvdyBtZXRob2QgMyBwYXJlY2Ugc2VyIHVtIGJvbSBuw7ptZXJvIGRlIGdydXBvcyBkZXZpZG8gw6AgcXVlZGEgZGUgMyBwYXJhIDQuCgojIyMgQmF5ZXNpYW4gSW5mb3JtYXRpb24gQ3JpdGVyaW9uCgpgYGB7ciByZXN1bHRzPUZBTFNFfQpkX2NsdXN0IDwtIE1jbHVzdChhcy5tYXRyaXgoc2NhbGVkX2RhdGEpLCBHPTE6MTUsIAogICAgICAgICAgICAgICAgICBtb2RlbE5hbWVzID0gbWNsdXN0Lm9wdGlvbnMoImVtTW9kZWxOYW1lcyIpKQoKYGBgCgpgYGB7cn0KcGxvdChkX2NsdXN0JEJJQykKYGBgCgoKKiBWaXN1YWxtZW50ZSBLID0gMyByZXByZXNlbnRhIHVtIGdhbmhvIG1haXMgc2lnbmlmaWNhdGl2byBlbSB0ZXJtb3MgZGUgQklDIChCYXllc2lhbiBJbmZvcm1hdGlvbiBDcml0ZXJpb24pIAoKIyMjIEh1YmVydCBJbmRleCBlIEQgSW5kZXgKCmBgYHtyfQpuYiA8LSBOYkNsdXN0KHNjYWxlZF9kYXRhLCBkaXNzPU5VTEwsIGRpc3RhbmNlID0gImV1Y2xpZGVhbiIsIAogICAgICAgICAgICAgIG1pbi5uYz0yLCBtYXgubmM9NSwgbWV0aG9kID0gImttZWFucyIsIAogICAgICAgICAgICAgIGluZGV4ID0gImFsbCIsIGFscGhhQmVhbGUgPSAwLjEpCmhpc3QobmIkQmVzdC5uY1sxLF0sIGJyZWFrcyA9IG1heChuYS5vbWl0KG5iJEJlc3QubmNbMSxdKSkpCmBgYAoKKiBPIMOtbmRpY2UgZGUgSHViZXJ0IGUgbyDDrW5kaWNlIEQgc3VnZXJlbSBLID0gNSAgY29tbyBhIG1lbGhvciBzb2x1w6fDo28KCjxicj4KCiMjIEsgRXNjb2xoaWRvCgo8YnI+Cgo+IE9wdGFyZW1vcyBwb3IgMyBncnVwb3MgcG9pcyBhIG1haW9yaWEgZG9zIHRlc3RlcyBhcG9udGEgbmVzc2EgZGlyZcOnw6NvLCBlIGVtcGlyaWNhbWVudGUgbsOjbyBmb2kgdmlzdG8gZ2FuaG8gbm8gdXNvIGRlIEs9NS4KCjxicj4KCioqKgoKPGJyPgoKIyBLLU1lYW5zIAoKPGJyPgoKIyMgQWdydXBhbWVudG8KCmBgYHtyfQpuX2NsdXN0ZXJzID0gMwoKc2NhbGVkX2RhdGEgJT4lCiAgICBrbWVhbnMobl9jbHVzdGVycywgaXRlci5tYXggPSAxMDAsIG5zdGFydCA9IDIwKSAtPiBrbQoKcCA8LSBhdXRvcGxvdChrbSwgZGF0YT1zY2FsZWRfZGF0YSwgZnJhbWUgPSBUUlVFKSAgCgpnZ3Bsb3RseShwKQoKYGBgCgoqIMOJIHBvc3PDrXZlbCB2ZXIgcXVlIGV4aXN0ZSB1bWEgcGFyY2VsYSBkZSBmaWxtZXMgY3VqYSBzZXBhcmHDp8OjbyBlbSB1bSBkYWRvIGdydXBvIG7Do28gZm9pIGNvbXBsZXRhbWVudGUgZmVsaXogcG9pcyBvcyBncnVwb3Mgc2Ugc29icmVww7VlLgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CnJvdy5uYW1lcyhzY2FsZWRfZGF0YSkgPC0gZGF0YSR0aXRsZQoKdG9jbHVzdCA8LSBzY2FsZWRfZGF0YSAlPiUgCiAgICByb3duYW1lc190b19jb2x1bW4odmFyID0gInRpdGxlIikgCgprbSA9IHRvY2x1c3QgJT4lIAogICAgc2VsZWN0KC10aXRsZSkgJT4lIAogICAga21lYW5zKGNlbnRlcnMgPSBuX2NsdXN0ZXJzLCBpdGVyLm1heCA9IDEwMCwgbnN0YXJ0ID0gMjApCgprbSAlPiUgCiAgICBhdWdtZW50KHRvY2x1c3QpICU+JSAKICAgIGdhdGhlcihrZXkgPSAidmFyacOhdmVsIiwgdmFsdWUgPSAidmFsb3IiLCAtdGl0bGUsIC0uY2x1c3RlcikgJT4lIAogICAgZ2dwbG90KGFlcyh4ID0gYHZhcmnDoXZlbGAsIHkgPSB2YWxvciwgZ3JvdXAgPSB0aXRsZSwgY29sb3VyID0gLmNsdXN0ZXIpKSArIAogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMikgKyAKICAgIGdlb21fbGluZShhbHBoYSA9IC41KSArIAogICAgZmFjZXRfd3JhcCh+IC5jbHVzdGVyKSArCiAgICBjb29yZF9mbGlwKCkKCmBgYAoKKioqCgo8YnI+CgokXGNvbG9ye3JlZH17XHRleHR7R3J1cG8gMX19JCAtICoqRW0gY2ltYSBkbyBtdXJvKioKCiAgKiBGaWxtZXMgbWVkaWFub3MgZW0gdGVybW9zIGRlIHByb3BvcsOnw6NvIGRlIHBlcnNvbmFnZW5zIGZlbWluaW5vcywgcHJvcG9yw6fDo28gZGUgZGlhbMOzZ29zIGRlZGljYWRvcyBhIHBlcnNvbmFnZW5zIGZlbWluaW5vcywgbcOpZGlhIGRlIGRpYWzDs2dvIGZlbWluaW5vIGUgZmF0dXJhbWVudG8uCiAgICAKPGJyPgoKYGBgCk8gbm9tZSBkbyBncnVwbyBzZSByZWZlcmUgw6AgZXhwcmVzc8OjbyBxdWUgc2lnbmlmaWNhIG7Do28gdG9tYXIgcGFydGlkby4KYGBgCgo8YnI+CgoqKioKCgokXGNvbG9ye2dyZWVufXtcdGV4dHtHcnVwbyAyfX0kIC0gKipXZSBDYW4gRG8gSXQhKioKCiAgKiBNZW5vciBGYXR1cmFtZW50byAKICAqIE1haXMgZGlhbMOzZ28gcGFyYSBhcyBtdWxoZXJlcyAKICAqIE1haW9yIHRheGEgZGUgcGVyc29uYWdlbnMgZmVtaW5pbm9zCiAgICAKPGJyPgoKKipXZSBDYW4gRG8gSXQhKiogw6kgbyBncnVwbyBkZSBmaWxtZXMgZGUgbWFpb3IgcmVwcmVzZW50YcOnw6NvIGZlbWluaW5hLCBxdWVyIHNlamEgZW0gcHJvcG9yw6fDo28gZGUgcGVyc29uYWdlbnMgZmVtaW5pbm9zIGNvbW8gZW0gcHJvcG9yw6fDo28gZSBtw6lkaWEgZGUgZGlhbMOzZ29zIGRlZGljYWRvcyBhIHBlcnNvbmFnZW5zIGZlbWluaW5vcy4gRXhpc3RlIHBvcsOpbSB1bWEgY2FyYWN0ZXLDrXN0aWNhIG5lZ2F0aXZhIHF1ZSBhY29tcGFuaGEgZXN0ZSBtZXNtbyBncnVwbywgcG9pcyBlc3RlIMOpIHRhbWLDqW0gbyBncnVwbyBkYXMgbWVub3JlcyB0YXhhcyBkZSBmYXR1cmFtZW50by4gSXNzbyBzdWdlcmUgdW1hIGluZmVsaXogYXNzb2NpYcOnw6NvIG5lZ2F0aXZhIGVudHJlIGEgcmVwcmVzZW50YcOnw6NvIGZlbWluaW5hIGVtIGZpbG1lcyBlIG8gZmF0dXJhbWVudG8gZGVzdGVzLiAKCjxicj4KCmBgYApPIG5vbWUgZG8gZ3J1cG8gc2UgcmVmZXJlIGFvIGZhbW9zbyBjYXJ0YXogZGUgSi4gSG93YXJkIE1pbGxlciBkZSAxOTQzIGluY2VudGl2YWRvIGFzIG11bGhlcmVzIGEgcGFydGljaXBhciBubyBlc2ZvcsOnbyBkZSBndWVycmEgbmFzIGbDoWJyaWNhcy4gCmBgYAoKPGJyPgoKKioqCgokXGNvbG9ye2JsdWV9e1x0ZXh0e0dydXBvIDN9fSQgLSAqKkl0J3MgQSBNYW4ncyBNYW4ncyBNYW4ncyBXb3JsZCoqICAgCgoqIE1haW9yIGZhdHVyYW1lbnRvIGVudHJlIHRvZG9zCiogTWVub3IgdGF4YSBkZSBkaWFsw7NnbyBwYXJhIGFzIG11bGhlcmVzCiogTWVub3IgdGF4YSBkZSBwZXJzb25hZ2VucyBmZW1pbmlub3MKICAgIAo8YnI+CgoqKkl0J3MgQSBNYW4ncyBNYW4ncyBNYW4ncyBXb3JsZCoqIMOpIG8gZ3J1cG8gZGUgZmlsbWVzIGRlIG1lbm9yIHJlcHJlc2VudGHDp8OjbyBmZW1pbmluYSwgcXVlciBzZWphIGVtIHByb3BvcsOnw6NvIGUgbcOpZGlhIGRlIHBlcnNvbmFnZW5zIGZlbWluaW5vcyBjb21vIGVtIHByb3BvcsOnw6NvIGRlIGRpYWzDs2dvcyBkZWRpY2Fkb3MgYSBwZXJzb25hZ2VucyBmZW1pbmlub3MuIEV4aXN0ZSBwb3LDqW0gdW1hIGNhcmFjdGVyw61zdGljYSBuZWdhdGl2YSBxdWUgYWNvbXBhbmhhIGVzdGUgbWVzbW8gZ3J1cG8sIHBvaXMgZXN0ZSDDqSB0YW1iw6ltIG8gZ3J1cG8gZGUgbWFpb3JlcyB0YXhhcyBkZSBmYXR1cmFtZW50by4gSXNzbyBzdWdlcmUgdW1hIGluZmVsaXogYXNzb2NpYcOnw6NvIHBvc2l0aXZhIGVudHJlIGF1c8OqbmNpYSBkZSAgcmVwcmVzZW50YcOnw6NvIGZlbWluaW5hIGVtIGZpbG1lcyBlIG8gZmF0dXJhbWVudG8gZGVzdGVzLiAKCjxicj4KCmBgYApPIG5vbWUgZG8gZ3J1cG8gc2UgcmVmZXJlIMOgIG3DunNpY2EgZGUgSmFtZXMgQnJvd24sIGEgcXVhbCBmb2kgZXNjcml0YSBwb3Igc3VhIGVudMOjbyBuYW1vcmFkYSBCZXR0eSBKZWFuIE5ld3NvbWUgY29tbyB1bSBjb21lbnTDoXJpbyBzb2JyZSBhIHJlbGHDp8OjbyBlbnRyZSBvcyBzZXhvcy4KYGBgCgoqKioKCjxicj4KCiMjIFF1YWxpZGFkZSBkYSBjbHVzdGVyaXphw6fDo28gLyBTaWxodWV0YQoKYGBge3J9CmRpc3RzID0gc2NhbGVkX2RhdGEgJT4lIAogIGRpc3QoKQoKc2NhbGVkX2RhdGEgJT4lCiAgICBrbWVhbnMoMywgaXRlci5tYXggPSAxMDAsIG5zdGFydCA9IDIwKSAtPiBrbQoKCnNpbGhvdWV0dGUoa20kY2x1c3RlciwgZGlzdHMpICU+JQogICBwbG90KGNvbCA9IFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCg0LCAiU2V0MiIpLGJvcmRlcj1OQSkKYGBgCgo8YnI+CgoqIE8gdmFsb3IgZGUgMC40NyBkYSBzaWxodWV0YSBzaWduaWZpY2EgcXVlIGEgbm9zc2EgY2x1c3Rlcml6YcOnw6NvIGZvaSByYXpvw6F2ZWwuIOODvijijJDilqBf4pagKeODjuKZqgoK